/**************************************************************
	CubeForm.as
	
	Created by:	Derrick Grigg
	derrick@dgrigg.com
	http://www.dgrigg.com
	
	Created on: April 18, 2007
	
	Version: 1.0.0
	
	This is released under a Creative Commons license. More information can be found here:
    http://creativecommons.org/licenses/by/2.5/

***************************************************************/
package com.dgrigg.containers
{
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.display.StageQuality;
	import flash.display.BitmapData;
	import flash.display.Bitmap;
	import flash.utils.clearInterval;
	import flash.utils.setInterval;
	import flash.geom.Point;
	import flash.events.Event;
	
	import mx.core.UIComponent;
	import mx.containers.ViewStack;
	import mx.core.Container;
	
	import org.papervision3d.cameras.Camera3D;
	import org.papervision3d.scenes.Scene3D;
	import org.papervision3d.objects.Plane;
	import org.papervision3d.objects.DisplayObject3D;
	import org.papervision3d.materials.BitmapMaterial;
	
	/**
	 * Dispatched after cube rotates to selected side.
	 */
	[Event(name="change",type="flash.events.Event")]
		
	/**
	 * Renders a view stack as a 3d cube using the PaperVision engine. 
	 * The cube can rotate to the 6 different sides to display a view
	 * from the view stack and allow the user to interact with it.
	 */
	public class CubeForm extends UIComponent
	{
		/**
		 * Papervision camerea.
		 */
		protected var camera:Camera3D;
		
		/**
		 * Papervision scene.
		 */
		protected var scene:Scene3D;
		
		/**
		 * Sprite that form is rendered in.
		 */
		protected var sprite:Sprite;
		
		/**
		 * Value to store a reference to the rotation interval.
		 */
		protected var intRotate:int;
		
		/**
		 * @private 
		 */
		protected var _selectedIndex: int = 0;
		
		/**
		 * Array of the six planes that make up the cube. 
		 * Each item in the array consists of a <code>plane</code> which is the Papervision Plane
		 * and a <code>view</code> which is a reference to the child in the view stack.
		 */
		protected var planes:Array;
		
		/**
		 * The view stack the form is bound to.
		 */
		protected var _viewStack:ViewStack;
		
		/**
		 * Root display object for the cube form.
		 */
		protected var rootNode:DisplayObject3D;
		
		/**
		 * Number of steps used to rotate the cube;
		 * 
		 * @default 10
		 */
		public var rotationSteps:int = 10;
		
		/**
		 * @param canvas The sprite to generate the cube form in.
		 */
		public function CubeForm(canvas:Sprite)
		{
			sprite = canvas;
			init();
		}

		private function init():void
		{
			var s:Stage = sprite.stage;
			s.quality = StageQuality.BEST;
			setupScene();
		}

		private function setupScene():void
		{
			scene = new Scene3D(sprite);


			camera = new Camera3D();
			camera.zoom = 10;
			camera.focus = 100;

			rootNode = scene.addChild(new DisplayObject3D(), 'rootNode');
			
			scene.renderCamera(camera);
		}
		
		/**
		 * The view stack that is bound to the cube form. 
		 * <ul>
		 * <li>The view stack must have atleast six children, otherwise an error will be thrown.</li>
		 * <li>The view stack must have it's <code>creationPolicy</code> property set to <code>all</code>.</li>
		 * <li>Each view in the view stack must have it's width and height explicity set, 
		 * otherwise the view does not get rendered properly during initialization.</li>
		 * </ul>
		 * 
		 */

		public function set viewStack (vs:ViewStack):void
		{
			var plane: Plane;
			var view:Container;
			
			planes = new Array();
			_viewStack = vs;
			
			//check to see if there are 6 views in the view stack
			//if no throw an error
			var children:Array = _viewStack.getChildren();
			if (children.length > 5)
			{
				view = children[0] as Container;
				plane = createPlane(view, 0 , 0 ,-(_viewStack.width/2));
				rootNode.addChild(plane, 'plane0');
				planes.push({plane:plane, view:view});	
				
				view = children[1] as Container;
				plane = createPlane(view, (_viewStack.width/2), 0, 0, 0, 270, 0);
				rootNode.addChild(plane, 'plane1');
				planes.push({plane:plane, view:view});	
				
				view = children[2] as Container;
				plane =createPlane(view, 0, 0, (_viewStack.width/2), 0, 180, 0);
				rootNode.addChild(plane, 'plane2');
				planes.push({plane:plane, view:view});	
				
				view = children[3] as Container;
				plane =createPlane(view, -(_viewStack.width/2), 0, 0, 0, 90, 0);
				rootNode.addChild(plane, 'plane3');
				planes.push({plane:plane, view:view});	
				
				view = children[4] as Container;
				plane =createPlane(view, 0, (_viewStack.width/2), 0, 270, 0, 0);
				rootNode.addChild(plane, 'plane4');
				planes.push({plane:plane, view:view});	
				
				view = children[5] as Container;
				plane =createPlane(view, 0, -(_viewStack.width/2), 0, 90, 0, 0);
				rootNode.addChild(plane, 'plane5');
				planes.push({plane:plane, view:view});	
				
				//render the scene to get determine the rendered dimensions
				scene.renderCamera( camera );
				
				//position the view stack directly over the cube form
				var p:Point = sprite.localToGlobal(new Point(0,0));
				_viewStack.x = p.x - _viewStack.width/2;
				_viewStack.y = p.y - _viewStack.height/2;			
				
				//adjust the camera zoom to compensate for any scaling that has occurred,
				//in order to keep the cube at the exact scale of the view stack
				camera.zoom = (_viewStack.width/sprite.width)*10;
				scene.renderCamera(camera);
			} else {
				var error:Error = new Error('Error: the viewStack must contain at least six children');
				throw(error);
			}

		}
		
		/**
		 * @private
		 */
		protected function createPlane(panel:UIComponent, pX:int=0, pY:int=0, pZ:int=0, pRotationX:int=0, pRotationY:int=0, pRotationZ:int=0):Plane
		{
			var bmp:BitmapData = new BitmapData(_viewStack.width, _viewStack.height);
			bmp.draw(panel);
			var material:BitmapMaterial = new BitmapMaterial(bmp);
			material.smooth = true;
			var initObj:Object = {x:pX, y:pY, z:pZ, rotationX:pRotationX, rotationY: pRotationY, rotationZ:pRotationZ};
			var plane:Plane = new Plane(material, bmp.width, bmp.height, 4, 4, initObj);
			return plane;
		}
		
		/**
		 * Index of selected plane on the cube form.
		 * 
		 * @default 0
		 */
		public function get selectedIndex():int
		{
			return _selectedIndex;
		}
		
		public function set selectedIndex(val:int):void
		{
			//get the selected plane and re-render that side in order 
			//to reflect the current data on the view
			var obj:Object = planes[_selectedIndex];
			var plane:Plane = obj.plane as Plane;
			var bmp:BitmapData = new BitmapData(obj.view.width, obj.view.height);
			bmp.draw(obj.view);
			var material:BitmapMaterial = new BitmapMaterial(bmp);
			plane.material = material;
			this.scene.renderCamera( camera );
			obj.view.visible = false;
			
			//get the current cube's rotation values
			_selectedIndex = val;
			var curRotation:Object = {x:rootNode.rotationX, y:rootNode.rotationY, z:rootNode.rotationZ};
			var destRotation:Object;
			
			switch (_selectedIndex)
			{
				case 0:
					destRotation = {x: 0, y:0, z:0};
					break;
				case 1:
					destRotation = {x: 0, y:90, z:0};
					break;
				case 2:
					destRotation = {x: 0, y:180, z:0};
					break;
				case 3:
					destRotation = {x: 0, y:270, z:0};
					break;
				case 4:
					destRotation = {x: 90, y:0, z:0};
					break;
				case 5:
					destRotation = {x: 270, y:0, z:0};
					break;
			}
			
			var steps: Object = new Object();
			steps.x = Math.floor((destRotation.x - curRotation.x)/rotationSteps);
			steps.y = Math.floor((destRotation.y - curRotation.y)/rotationSteps);
			steps.z = Math.floor((destRotation.z - curRotation.z)/rotationSteps);
			
			intRotate = setInterval(rotateCube, 50, steps, destRotation);
		}
		
		private function rotateCube(steps:Object, dest:Object):void
		{
			if (Math.abs(rootNode.rotationX) != dest.x) rootNode.rotationX += steps.x;
			if (Math.abs(rootNode.rotationY) != dest.y) rootNode.rotationY += steps.y;
			if (Math.abs(rootNode.rotationX) != dest.z) rootNode.rotationZ += steps.z;
			
			scene.renderCamera( camera );
			
			if (Math.abs(rootNode.rotationX) == dest.x && Math.abs(rootNode.rotationY) == dest.y && Math.abs(rootNode.rotationZ) == dest.z)
			{
				//reset the rotation values back to a value <= 360
				if (Math.abs(rootNode.rotationX) >= 360) rootNode.rotationX = Math.abs(rootNode.rotationX) - 360;
				if (Math.abs(rootNode.rotationY) >= 360) rootNode.rotationY = Math.abs(rootNode.rotationY) - 360;
				if (Math.abs(rootNode.rotationZ) >= 360) rootNode.rotationZ = Math.abs(rootNode.rotationZ) - 360;
				
				clearInterval(intRotate);
				_viewStack.selectedIndex = _selectedIndex;
				dispatchEvent(new Event(Event.CHANGE));
			}
		}
		
	}
}